package utils;


import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.emf.common.util.Enumerator;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EEnum;
import org.eclipse.emf.ecore.EFactory;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;

public class Utils {

	/*
	 * FICHIERS
	 */
	
	public static EPackage loadEcore(String file) {
		Resource.Factory.Registry reg = Resource.Factory.Registry.INSTANCE;
		reg.getExtensionToFactoryMap().put("ecore", new XMIResourceFactoryImpl());
	
		ResourceSet resource_set = new ResourceSetImpl();
		URI ecoreFileURI = URI.createFileURI(file);
		Resource model_resource = resource_set.getResource(ecoreFileURI,true);
		
		// package racine
		return (EPackage)model_resource.getContents().get(0);
	}
	
	@SuppressWarnings("unchecked")
	public static Resource loadInstance(String file, String extension, EPackage pack) {
		URI fileURI = URI.createFileURI(new File(file + "." + extension).getAbsolutePath());

		ResourceSet resource_set = new ResourceSetImpl();
		resource_set.getPackageRegistry().put(pack.getNsURI(), pack);

		Resource.Factory.Registry reg = Resource.Factory.Registry.INSTANCE;
		Map m = reg.getExtensionToFactoryMap();
		m.put(extension, new XMIResourceFactoryImpl());

		Resource resource = resource_set.getResource(fileURI, true);
		
		return resource;
	}
	
	@SuppressWarnings("unchecked")
	public static void saveInstance(String file, String extension, EObject root, String uri, EPackage pack) {
		ResourceSet resource_set = new ResourceSetImpl();
		resource_set.getPackageRegistry().put(
				uri,
				pack);
		resource_set.getResourceFactoryRegistry().getExtensionToFactoryMap().put(
				Resource.Factory.Registry.DEFAULT_EXTENSION,
				new XMIResourceFactoryImpl());

		/*
		Resource.Factory.Registry reg = Resource.Factory.Registry.INSTANCE;
		Map m = reg.getExtensionToFactoryMap();
		m.put(extension, new XMIResourceFactoryImpl());
		*/
		
		URI fileURI = URI.createFileURI(file + "." + extension);
		Resource resource = resource_set.createResource(fileURI);

		// save
		// System.out.println("Resource: " + resource);
		resource.getContents().add(root);

		Map options = new HashMap();
		// options.put(XMIResource.OPTION_DECLARE_XML, Boolean.TRUE);
		options.put(XMLResource.OPTION_ENCODING, "UTF-8");

		try {
			resource.save(options);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/*
	 * CLASSE
	 */
	public static EClass getClassFromName(EPackage pkg, String name) {
		for (EClassifier ec: pkg.getEClassifiers()) {
			if (ec instanceof EClass) {
				if (name.equals(ec.getName())) {
					return (EClass)ec;
				}
			}
		}
		
		for (EPackage p: pkg.getESubpackages()) {
			return getClassFromName(p, name);
		}
		
		return null;
	}
	
	/*
	 * ACCESSEURS
	 */
	
	public static EAttribute getAttribute(EClass c, String att) {
//		System.out.println("===> getAttribute: " + c.getName() + " " + att);
		for (EAttribute a: c.getEAllAttributes()) {
			if (att.equals(a.getName())) {
				return a;
			}
		}
		return null;
	}
	
	public static boolean isString(EAttribute att) {
//		System.out.println("###> " + att);
		return att.getEType().getName().equals("EString");
	}
	
	public static boolean isInteger(EAttribute att) {
		return att.getEType().getName().equals("EInt");
	}
	
	public static boolean isBoolean(EAttribute att) {
		return att.getEType().getName().equals("EBoolean");
	}
	
	public static boolean isEnumerate(EAttribute att) {
		return (att.getEType() instanceof EEnum);
	}

	public static boolean setAttribute(EObject obj, String att, String value, List<EFactory> factories) {
		EAttribute attribute = getAttribute(obj.eClass(), att);
		if (attribute == null)
			return false;
		return setAttribute(obj, attribute, value, factories);
	}
	
	@SuppressWarnings("unchecked")
	public static boolean setAttribute(EObject obj, EAttribute att, String value, List<EFactory> factories) {
//		System.out.println("===> setAttribute: " + obj + " | " + att.getName() + " | " + value);
		
		if (att == null)
			return false;
		
		if (isString(att)) {
			if (att.isMany()) {
				List<String> values = (List<String>)obj.eGet(att);
				values.add(value);
			}
			else {
				obj.eSet(att, value);
			}
			return true;
		}
		else if (isInteger(att)) {
			if (att.isMany()) {
				List<Integer> values = (List<Integer>)obj.eGet(att);
				values.add(new Integer(value));
			}
			else {
				if (value != null)
					obj.eSet(att, Integer.parseInt(value));
			}
			return true;
		}
		else if (isBoolean(att)) {
			if (att.isMany()) {
				List<Boolean> values = (List<Boolean>)obj.eGet(att);
				values.add(new Boolean(value));
			}
			else {
				obj.eSet(att, Boolean.parseBoolean(value));
			}
			return true;
		}
		else if (isEnumerate(att)) {
			Enumerator e = createEnumerator((EEnum)att.getEType(), value, factories);
			System.out.println("===> " + e);
			obj.eSet(att, e);
		}
		
		return false;
	}
	
	public static boolean setAttributes(EObject obj, List<String> attributes, List<String> values, List<EFactory> factories) {
		boolean b = true;
		for (int i = 0; i < attributes.size(); i++) {
			b = b && setAttribute(obj, getAttribute(obj.eClass(), attributes.get(i)), values.get(i), factories);
		}
		return b;
	}
	
	public static boolean isAttribute(EClass c, String rel) {
		for (EAttribute att: c.getEAllAttributes()) {
			if (rel.equals(att.getName()))
				return true;
		}
		return false;
	}
	
	public static boolean isReference(EClass c, String rel) {
		for (EReference ref: c.getEAllReferences()) {
			if (rel.equals(ref.getName()))
				return true;
		}
		return false;
	}
	
	/*
	 * REFERENCES
	 */
	
	public static EReference getReference(EClass c, String ref) {
//		System.out.println("===> getReference: " + c.getName() + " " + ref);
		for (EReference r: c.getEAllReferences()) {
			if (ref.equals(r.getName())) {
				return r;
			}
		}
		return null;
	}
	
	public static boolean setReference(EObject src, String ref, EObject dst) {
		EReference reference = getReference(src.eClass(), ref);
		if (reference == null) {
			return false;
		}
		setReference(src, reference, dst);
		
		return true;
	}
	
	@SuppressWarnings("unchecked")
	public static void setReference(EObject src, EReference ref, EObject dst) {
//		System.out.println("===> " +  src.eClass().getName() + " | " + ref.getName() + " | " + dst.eClass().getName());
		if (ref.isMany()) {
			List<EObject> os = (List<EObject>)src.eGet(ref);
			os.add(dst);
		}
		else {
			src.eSet(ref, dst);
		}
	}
	
	/*
	 * ARBRE
	 */
	
	public static EObject getObject(EObject obj, String ref, int index) {
//		System.out.println("===> " + obj + " " + ref + " " + index);
		EReference reference;
		if (ref.charAt(0) == '.')
			reference = getReference(obj.eClass(), ref.substring(1));
		else
			reference = getReference(obj.eClass(), ref);
		return getObject(obj, reference, index);
	}
	
	public static EObject getObject(EObject obj, EReference ref) {
		return getObject(obj, ref, 0);
	}
	
	@SuppressWarnings("unchecked")
	public static EObject getObject(EObject obj, EReference ref, int index) {
		if (ref.isMany()) {
			List<EObject> os = (List<EObject>)obj.eGet(ref);
			if (os.size() <= index) {
				return null;
			}
			else {
				return os.get(index);
			}
		}
		else if (index == 0) {
			EObject o = (EObject)obj.eGet(ref);
			return o;
		}
		return null;
	}
	
	public static boolean verify(EObject obj, String att, String value) {
//		System.out.println("###> " + obj + " | " + att + " | " + value);
		EAttribute attribute = getAttribute(obj.eClass(), att);
		
		if (isString(attribute)) {
			return value.equals((String)obj.eGet(attribute));
		}
		else if (isInteger(attribute)) {
			int i = Integer.parseInt(value);
			int j = (Integer)obj.eGet(attribute);
			return i == j;
		}
		else if (isBoolean(attribute)) {
			boolean b = Boolean.parseBoolean(value);
			boolean bb = (Boolean)obj.eGet(attribute);
			return b == bb;
		}
		return false;
	}
	
	@SuppressWarnings("unchecked")
	public static EObject getObject(EObject obj, EReference ref, List<String> attributes, List<String> values) {
//		System.out.println("===> getObject: " + obj + " | " + ref + " | " + attributes + " | " + values);
		if (ref.isMany()) {
			List<EObject> os = (List<EObject>)obj.eGet(ref);
			for (EObject o: os) {
				boolean ok = true;
				for (int i = 0; i < attributes.size(); i++) {
					if (!verify(o, attributes.get(i), values.get(i))) {
						ok = false;
						break;
					}
				}
				if (ok)
					return o;
			}
		}
		else {
			EObject o = (EObject)obj.eGet(ref);
			for (int i = 0; i < attributes.size(); i++) {
				if (!verify(o, attributes.get(i), values.get(i)))
					return null;
			}
		}
		return null;
	}
	
	public static EObject getObject(EObject obj, String ref_cond) {
		// System.out.println("###> " + obj + " " + ref_cond);
		int i = ref_cond.indexOf('(');
		int j = ref_cond.indexOf('[');
		
		if ((j != -1) && ((i == -1) || (j < i))) {
			int k = ref_cond.indexOf(']');
			EObject o = getObject(obj, ref_cond.substring(0, j), Integer.parseInt(ref_cond.substring(j+1, k)));
			// milieu du path
			if (k != ref_cond.length()-1)
				return getObject(o, ref_cond.substring(k+1));
			// fin du path
			else 
				return o;
		}
		else if ((j == -1) && (i ==-1)) {
			return getObject(obj, getReference(obj.eClass(), ref_cond));
		}

		String ref = ref_cond.substring(0, i);
		String cond = ref_cond.substring(i+1, ref_cond.length()-1);
		String[] conds = cond.split(",");
		List<String> attributes = new ArrayList<String>();
		List<String> values = new ArrayList<String>();
		
		for (String s : conds) {
			i = s.indexOf('=');
			attributes.add(s.substring(0, i));
			values.add(s.substring(i+1));
		}
		return getObject(obj, getReference(obj.eClass(), ref), attributes, values);
	}
	
	public static EObject getObject(EObject obj, String path, List<EFactory> factories) {
		// TODO
		System.out.println("===> getObject: " + obj + " " + path);
		String[] paths = path.split("\\.");
		EObject next = null;
		
		for (String p: paths) {
			next = getObject(obj, p);
			if (next == null) {
				next = createObject(obj, p, factories);
			}
			obj = next;
		}
		return obj;
	}
	
	@SuppressWarnings("unchecked")
	public static EObject getObject(EObject root, List<String> path, List<Integer> index) {
		EObject container = root;
		EObject eo = null;
		
		for (int i = 0; i < path.size(); i++) {
			String p = path.get(i);
			EReference ref = (EReference)container.eClass().getEStructuralFeature(p);
			
			if (ref.isMany()) {
				List<EObject> temp = (List<EObject>)container.eGet(ref);
				eo = temp.get(index.get(i));
			}
			else {
				eo = (EObject)container.eGet(ref);
			}
			container = eo;
		}
		return eo;
	}
	
	@SuppressWarnings("unchecked")
	public static int getObjectIndex(EObject container, EReference ref, EObject eo) {
		if (ref.isMany()) {
			List<EObject> temp = (List<EObject>)container.eGet(ref);
			for (int i = 0; i < temp.size(); i++) {
				if (temp.get(i) == eo)
					return i;
			}
		}
		else {
			EObject temp = (EObject)container.eGet(ref);
			if (temp == eo)
				return 0;
		}
		return -1;
	}
	
	/*
	 * CREATION
	 */
	@SuppressWarnings("unchecked")
	public static Enumerator createEnumerator(EEnum type, String value, List<EFactory> factories) {
		for (EFactory factory: factories) {
			try {
				String name = "create" + type.getName() + "FromString";
				Class[] ptypes = { EDataType.class, String.class};
				Method m = factory.getClass().getMethod(name, ptypes);
				Object[] params = { type, value };
				Enumerator e = (Enumerator)m.invoke(factory, params);
				return e;
			} catch (SecurityException e) {
				e.printStackTrace();
			} catch (NoSuchMethodException e) {
				// e.printStackTrace();
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				e.printStackTrace();
			}
		}
		return null;
	}
	
	public static EObject createObject(String type, List<EFactory> factories) {
//		System.out.println("===> createObject: " + type);
		for (EFactory factory: factories) {
			try {
				Method m = factory.getClass().getMethod("create" + type, (Class[])null);
				EObject obj = (EObject)m.invoke(factory, (Object[])null);
				return obj;
			} catch (SecurityException e) {
				e.printStackTrace();
			} catch (NoSuchMethodException e) {
				// e.printStackTrace();
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				e.printStackTrace();
			}
		}
		return null;
	}
	
	public static EObject createObject(EObject obj, String ref_cond, List<EFactory> factories) {
		// TODO
		System.out.println("===> createObject: " + obj + " | " + ref_cond);
		EObject n_obj = null;
		EReference ref;
		String[] conds = null;;
		
		int i = ref_cond.indexOf('(');

		// pas de conditions
		if (i == -1) {
			ref = getReference(obj.eClass(), ref_cond.substring(0, ref_cond.indexOf('[')));
		}
		else {
			ref = getReference(obj.eClass(), ref_cond.substring(0, i));
			String cond = ref_cond.substring(i+1, ref_cond.length()-1);
			conds = cond.split(",");
		}

		n_obj = createObject(ref.getEType().getName(), factories);
		setReference(obj, ref, n_obj);
		
		if (conds == null)
			return n_obj;
		
		List<String> attributes = new ArrayList<String>();
		List<String> values = new ArrayList<String>();
		
		for (String s : conds) {
			i = s.indexOf('=');
			attributes.add(s.substring(0, i));
			values.add(s.substring(i+1, s.length()));
		}
		setAttributes(n_obj, attributes, values, factories);
		
		return n_obj;
	}
	
	/*
	 * 
	 */
	public static EPackage getRootPackage(EPackage pack) {
		EPackage pkg = pack;
		while (pkg.getESuperPackage() != null)
			pkg = pkg.getESuperPackage();
		return pkg;
	}

}
